﻿using OnPowerShapeAddin.ObjectModel.OneNote2010XMLSchema;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace OnPowerShapeAddin.Operations
{
    class GroupingOperation
    {
        public byte[] GroupElements(IEnumerable<PageObject> elements, string ungroupedElementsXml)
        {
            DrawingVisual drawingVisual = new DrawingVisual();

            using (DrawingContext dc = drawingVisual.RenderOpen())
            {
                foreach (var po in elements)
                {
                    DrawPageObject(dc, po);
                }
            }

            drawingVisual.Transform = new TranslateTransform(-Math.Abs(drawingVisual.ContentBounds.X),
                                                             -Math.Abs(drawingVisual.ContentBounds.Y));

            int width = (int)Math.Ceiling(drawingVisual.ContentBounds.Width);
            int height = (int)Math.Ceiling(drawingVisual.ContentBounds.Height);
            int dpi = 96;

            if (width <= 0 || height <= 0)
                return null;

            RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Default);
            bitmap.Render(drawingVisual);

            BitmapMetadata bmpMeta = new BitmapMetadata("png");
            bmpMeta.SetQuery("/tEXt/{str=PowerShapeUngroupData}", ungroupedElementsXml);

            PngBitmapEncoder encoder = new PngBitmapEncoder();
            BitmapFrame frame = BitmapFrame.Create(bitmap, null, bmpMeta, null);
            encoder.Frames.Add(frame);

            using (MemoryStream mem = new MemoryStream())
            {
                encoder.Save(mem);

#if DEBUG
                string dir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
                File.WriteAllBytes(Path.Combine(dir, "group.png"), mem.ToArray());
#endif

                return mem.ToArray();
            }
        }


        public void DrawPageObject(DrawingContext dc, PageObject po)
        {
            // convert one note positions into pixel coordinates
            // translate element to onenote position coordinates
            // this is only required if we have more than one shape ???
            // skiping this translation would mess up positions in the generated image
            TransformGroup transform = new TransformGroup();
            transform.Children.Add(new TranslateTransform(po.Position.x * 1.3333d, po.Position.y * 1.3333d));

            dc.PushTransform(transform);

            if (po is InkDrawing)
            {
                DrawInkDrawing(dc, (InkDrawing)po);
            }
            else if (po is Image)
            {
                DrawImage(dc, (Image)po);
            }
            else if (po is Outline)
            {

            }
            else
            {
                // InsertedFile and MediaFile are not supported
            }

            dc.Pop();
        }

        public void DrawInkDrawing(DrawingContext dc, InkDrawing inkDraw)
        {
            byte[] inkData = inkDraw.Item as byte[];

            if (inkData == null)
                return;

            using (MemoryStream ms = new MemoryStream(inkData))
            {
                StrokeCollection strokes = new StrokeCollection(ms);

                // every StrokePoint has a absolut coordinate
                // so we need to translate the ink to (0,0)
                // and use the position from onenote
                // applied in DrawPageObject()
                Rect bounds = strokes.GetBounds();

                TransformGroup transform = new TransformGroup();
                transform.Children.Add(new TranslateTransform(-bounds.X, -bounds.Y));
                    
                // the ink data holds always the 'original' shape
                // thus if the shape was scaled in onenote we have to
                // scale the ink data to match the actual size in onenote
                // bounds will increase with thicker lines but inkDraw.Size not
                double lineWidth = strokes[0].DrawingAttributes.Width;
                double lineHeight = strokes[0].DrawingAttributes.Height;
                
                ScaleTransform scaleTransform = GetScaleTransform(inkDraw, bounds.Width - lineWidth, bounds.Height - lineHeight);

                if (scaleTransform.ScaleX != 0.0 && scaleTransform.ScaleY != 0.0)
                    transform.Children.Add(scaleTransform);

                strokes.Transform(transform.Value, false);

                strokes.Draw(dc);
            }
        }

        public void DrawImage(DrawingContext dc, Image img)
        {
            byte[] bmpData = img.Item as byte[];

            if (bmpData == null)
                return;

            if (img.format == "auto")
            {
                // TODO: don't know what auto should be?
                System.Diagnostics.Debug.WriteLine("auto format are not supported at the moment");
                return;
            }

            if (img.format == "emf")
            {
                // WPF has no support for EMF (for a good reason)
                // so we take a small trip into the GDI+ world
                // and convert the EMF to a PNG
                bmpData = ConvertEmfToBitmap(bmpData);
            }
            
            using (MemoryStream ms = new MemoryStream(bmpData))
            {
                BitmapImage bmp = new BitmapImage();
                bmp.BeginInit();
                bmp.CacheOption = BitmapCacheOption.OnLoad;
                bmp.StreamSource = ms;
                bmp.EndInit();

                ScaleTransform scaleTransform = GetScaleTransform(img, bmp.PixelWidth, bmp.PixelHeight);

                if (scaleTransform.ScaleX != 0.0 && scaleTransform.ScaleY != 0.0)
                {
                    dc.PushTransform(scaleTransform);
                    dc.DrawImage(bmp, new Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight));
                    dc.Pop();
                }
                else
                {
                    dc.DrawImage(bmp, new Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight));
                }
            }            
        }

        public string UngroupElements(byte[] imgBytes)
        {
            /*
             * 1. Decode image bytes
             * 2. Extracted stored XML from png metadata
             * 3. Return it
             */

            using (MemoryStream mem = new MemoryStream(imgBytes))
            {
                PngBitmapDecoder decoder = new PngBitmapDecoder(mem, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);

                BitmapFrame frame = decoder.Frames.FirstOrDefault();
                BitmapMetadata bmpMeta = (BitmapMetadata)frame.Metadata;

                return (string)bmpMeta.GetQuery("/tEXt/{str=PowerShapeUngroupData}");
            }
        }

        private ScaleTransform GetScaleTransform(PageObject po, double width, double height)
        {
            double scaleX = 0.0;
            double scaleY = 0.0;

            if (Math.Abs((po.Size.width * 1.3333d) - width) > 0.1)
                scaleX = (po.Size.width * 1.3333d) / width;
            if (Math.Abs((po.Size.height * 1.3333d) - height) > 0.1)
                scaleY = (po.Size.height * 1.3333d) / height;

            return new ScaleTransform(scaleX, scaleY);
        }

        private byte[] ConvertEmfToBitmap(byte[] emfData)
        {
            using (MemoryStream ms = new MemoryStream(emfData))
            {
                using (System.Drawing.Imaging.Metafile mf = new System.Drawing.Imaging.Metafile(ms))
                {
                    using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(mf.Width, mf.Height))
                    {
                        using (System.Drawing.Graphics gfx = System.Drawing.Graphics.FromImage(bmp))
                        {
                            gfx.DrawImage(mf, System.Drawing.Point.Empty);
                        }

                        using (MemoryStream ouput = new MemoryStream())
                        {
                            bmp.Save(ouput, System.Drawing.Imaging.ImageFormat.Png);

                            return ouput.ToArray();
                        }
                    }
                }
            }
        }
    }
}
